﻿<!DOCTYPE HTML>
<html lang="zh">
<head>
<title>CallbackCreate - 语法 &amp; 使用 | AutoHotkey v2</title>
<meta name="description" content="The CallbackCreate function creates a machine-code address that when called, redirects the call to a function in the script." />
<meta name="ahk:equiv-v1" content="commands/RegisterCallback.htm" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="../static/theme.css" rel="stylesheet" type="text/css" />
<script src="../static/content.js" type="text/javascript"></script>
<script type="text/javascript">$(function(){0<=window.navigator.userAgent.toLowerCase().indexOf("ucbrowser")&&CaoNiMaDeUc()})</script>
</head>
<body>

<h1>CallbackCreate</h1>

<p>创建机器码地址, 当它被调用时会重定向到脚本中的<a href="../Functions.htm">函数</a>.</p>

<pre class="Syntax">Address := <span class="func">CallbackCreate</span>(Function <span class="optional">, Options := "", ParamCount := Function.MinParams</span>)</pre>
<h2 id="Parameters">参数</h2>
<dl>

  <dt>Function</dt>
  <dd>
    <p>类型: <a href="../Concepts.htm#strings">字符串</a>或<a href="../Concepts.htm#objects">对象</a></p>
    <p>函数名或<a href="../objects/Functor.htm">函数对象</a>. 每当调用 <em>Address</em> 时会自动调用此函数. 此函数同时会接收到传递给 <em>Address</em> 的参数.</p>
    <p><a href="../Functions.htm#closures">闭包</a>或<a href="../objects/Functor.htm#BoundFunc">绑定函数</a>可以用来区分多个回调函数调用相同的脚本函数.</p>
    <p>回调函数保留对函数对象的引用, 并在脚本调用 <a href="#CallbackFree">CallbackFree</a> 时释放它.</p>
  </dd>

  <dt>Options</dt>
  <dd>
    <p>类型: <a href="../Concepts.htm#strings">字符串</a></p>
    <p>指定零个或多个下列单词或字符串. 在选项间使用空格分隔(例如 <code>"C Fast"</code>).</p>
    <p id="Fast"><strong>Fast</strong> 或 <strong>F</strong>: 避免每次调用 <em>Function</em> 时都启动新的<a href="../misc/Threads.htm">线程</a>. 尽管这样执行的更好, 但必须避免调用 <em>Address</em> 的线程发生变化(例如当回调函数被传入的消息触发). 这是因为 <em>FunctionName</em> 能改变在它被调用时正在运行的线程的全局设置(例如 <a href="../Variables.htm#LastError">A_LastError</a> 和<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>). 有关详情, 请参阅<a href="#Threads">备注</a>.</p>
    <p><strong>CDecl</strong> 或 <strong>C</strong>: 让 <em>Address</em> 遵循 &quot;C&quot; 调用约定. 此选项通常省略, 因为在回调函数中使用标准调用约定更为常用. 64 位版本的 AutoHotkey 会忽略这个选项, 它使用 x64 调用约定.</p>
    <p><strong>&amp;</strong>: 将参数列表的地址(单个整数) 传递给 <em>Function</em>, 而不是传递给各个参数. 可以使用 <a href="NumGet.htm">NumGet</a> 检索参数值. 当使用标准的 32 位调用约定时, <em>ParamCount</em> 必须以 DWORDs 指定参数列表的大小(字节数除以 4).</p>
  </dd>

  <dt>ParamCount</dt>
  <dd>
    <p>类型: <a href="../Concepts.htm#numbers">整数</a></p>
    <p><em>Address</em> 的调用者会传递给它的参数数目. 如果省略, 则它默认为 <code><i>Function</i>.<a href="../objects/Func.htm#MinParams">MinParams</a></code>, 这通常是 <em>Function</em> <a href="../Functions.htm#define">定义</a>中强制参数的数目. 在这两种情况中, 必须确保调用者准确传递此数目的参数.</p>
  </dd>

</dl>

<h2 id="Return_Value">返回值</h2>
<p>类型: <a href="../Concepts.htm#numbers">整数</a></p>
<p>CallbackCreate 返回一个机器码地址. 此地址通常通过 <a href="DllCall.htm">DllCall</a> 或使用 <a href="NumPut.htm">NumPut</a> 放置在结构中传递给外部函数, 但也可以由 DllCall 直接调用. 将地址传递给 <a href="#CallbackFree">CallbackFree</a> 将删除回调.</p>

<h2 id="Error_Handling">错误处理</h2>
<p>在下列任何一种情况下, 此函数失败并抛出异常:</p>
<ul>
  <li><em>Function</em> 不是对象或有效的函数名, 或既没有 <code>MinParams</code> 属性, 也没有 <code>Call</code> 方法.</li>
  <li><em>Function</em> 具有 <code>MinParams</code> 属性, 该属性超过回调所提供的参数数量.</li>
  <li><em>ParamCount</em> 为负数.</li>
  <li><em>ParamCount</em> 省略, 并且: 1) <em>Function</em> 没有 <code>MinParams</code> 属性; 或 2) <code>&amp;</code> 选项与标准 32 位调用约定一起使用.</li>
</ul>

<h2 id="The_Callback_Functions_Parameters">回调函数的参数</h2>
<p>分配给回调地址的<a href="../Functions.htm">函数</a>最多可以接受 31 个参数.  允许使用<a href="../Functions.htm#optional">可选参数</a>, 当多个调用者调用该函数时, 这非常有用.</p>
<p>正确地解析参数需要了解 x86 调用约定是如何工作的. 由于 AutoHotkey 没有类型化参数, 因此假定回调的参数列表由整数组成, 可能需要进行一些重新解释.</p>

<p><strong>AutoHotkey 32-bit:</strong> 所有传入参数都是无符号 32 位整数. 较小的类型被填充为 32 位, 而较大的类型被分割为一系列 32 位的参数.</p>
<p>如果传入的参数是一个有符号整数, 任何负数都可以通过下面的例子来表示:</p>
<pre><em>; 方法 #1</em>
if (wParam &gt; 0x7FFFFFFF)
    wParam := -(~wParam) - 1

<em>; 方法 #2: 依赖于 AutoHotkey 原生使用带符号的 64 位整数这一事实.</em>
wParam := wParam &lt;&lt; 32 &gt;&gt; 32</pre>

<p><strong>AutoHotkey 64-bit:</strong> 所有传入的参数都是有符号的 64 位整数. AutoHotkey 本身不支持无符号 64 位整数. 较小的类型被填充为 64 位，而较大的类型总是通过地址传递.</p>

<p><strong>AutoHotkey 32-bit/64-bit:</strong> 如果传入的参数是 8 位或 16 位(或 x64 上的 32 位), 则值的上位可能包含 "garbage" 可以使用 bitwise-and 进行过滤, 如下例所示:</p>
<pre>Callback(UCharParam, UShortParam, UIntParam) {
    UCharParam &amp;= 0xFF
    UShortParam &amp;= 0xFFFF
    UIntParam &amp;= 0xFFFFFFFF
    <em>;...</em>
}</pre>
<p>如果调用者希望传入参数是字符串, 那么它实际接收的是字符串的地址. 要获取这样的字符串, 请使用 <a href="StrGet.htm">StrGet</a>:</p>
<pre>MyString := StrGet(MyParameter)</pre>
<p>如果某个传入参数为结构的地址, 则可以参照 <a href="DllCall.htm#struct">DllCall 结构</a>中描述的步骤提取其中的各个成员.</p>

<p id="Indirect"><strong>按址接收参数:</strong> 如果使用 <code>&amp;</code> 选项, 函数接收第一个回调参数的 <i>地址</i>. 例如:</p>
<pre>
callback := CallbackCreate("TheFunc", "F&amp;", 3)  <em>; 32 位中必须指定参数列表的大小.</em>
DllCall(callback, &quot;float&quot;, 10.5, &quot;int64&quot;, 42)
TheFunc(params) {
    MsgBox <a href="NumGet.htm">NumGet</a>(params, 0, "float") ", " NumGet(params, A_PtrSize, "int64")
}</pre>
<p>32 位程序中的大多数回调都使用 <em>stdcall</em> 调用约定, 该约定需要固定数量的参数. 在这种情况下, 必须将 <em>ParamCount</em> 设置为参数列表的大小, 其中 Int64 和 Double 都计为两个 32 位参数. 使用 <em>Cdecl</em> 或 64 位调用约定时, <em>ParamCount</em> 无效.</p>

<h2>函数应该 <em>Return</em> 什么</h2>
<p>如果函数使用不带任何参数的 <a href="Return.htm">Return</a>, 或者指定空白值, 如 &quot;&quot;(或者根本不使用 Return), 则将 0 返回给回调的调用者. 否则, 函数应该返回一个整数, 然后将其返回给调用者. AutoHotkey 32-bit 将返回值截断为 32 位, 而 AutoHotkey 64-bit 则支持 64 位返回值. 不支持返回大于此值的结构(按值).</p>

<h2 id="Threads">Fast vs. Slow</h2>
<p>默认/慢速模式使该函数以默认值(如 <a href="SendMode.htm">SendMode</a> 和 <a href="DetectHiddenWindows.htm">DetectHiddenWindows</a>) 重新启动. 这些默认值可以在<a href="../Scripts.htm#auto">脚本启动</a>中更改.</p>
<p>与之相比, <a href="#Fast">快速模式</a>会继承在调用函数时正在运行的<a href="../misc/Threads.htm">线程</a>的全局设置. 而且, 函数对全局设置的任何修改(包括<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>) 都会在<a href="../misc/Threads.htm">当前线程</a>生效. 因此, 快速模式应该只在确切知道函数会被哪个线程调用时才使用.</p>
<p>要避免被自己(或其他任何线程) 中断, 回调可以在它的首行使用 <a href="Critical.htm">Critical</a>. 但是, 通过小于 0x312 消息达到的间接调用函数时, 这并不是完全有效的(增加 Critical 的 <a href="Critical.htm#Interval">interval</a> 也许有帮助). 而且, <a href="Critical.htm">Critical</a> 不会阻止执行一些可能导致间接调用它自己的动作, 例如调用 <a href="SendMessage.htm">SendMessage</a> 或 <a href="DllCall.htm">DllCall</a>.</p>

<h2 id="CallbackFree">内存</h2>
<p>每次使用 CallbackCreate 都会分配少量内存(32 字节加上系统开销). 由于操作系统会在脚本退出时自动释放该内存, 因此分配了少量, <em>固定</em> 数量的回调的任何脚本都不必显式释放内存. 相比之下, 不确定/无限制次数调用 CallbackCreate 的脚本应在任何未使用的回调中显式调用以下代码:</p>
<pre>CallbackFree(Address)</pre>
<p>这也会释放对脚本的函数对象的回调引用.</p>

<h2 id="Related">相关</h2>
<p><a href="DllCall.htm">DllCall</a>, <a href="OnMessage.htm">OnMessage</a>, <a href="OnExit.htm">OnExit</a>, <a href="OnClipboardChange.htm">OnClipboardChange</a>, <a href="Sort.htm#callback">Sort's callback</a>, <a href="Critical.htm">Critical</a>, <a href="PostMessage.htm">PostMessage</a>, <a href="SendMessage.htm">SendMessage</a>, <a href="../Functions.htm">Functions</a>, <a href="../misc/SendMessageList.htm">List of Windows Messages</a>, <a href="../misc/Threads.htm">Threads</a></p>

<h2 id="Examples">示例</h2>
<div class="ex" id="ExWinList">
<p><a href="#ExWinList">#1</a>: 下面是个可运行脚本, 它显示了所有顶层窗口的摘要.</p>
<pre><em>; 考虑到性能和内存的保持, 只为指定的回调调用一次 CallbackCreate:</em>
if not EnumAddress  <em>; 由于只能从这个线程调用, 所以可以使用快速模式:</em>
    EnumAddress := <strong>CallbackCreate</strong>(&quot;EnumWindowsProc&quot;, &quot;Fast&quot;)

DetectHiddenWindows True  <em>; 由于是快速模式, 所以此设置也会在回调中生效.</em>

<em>; 把控制传给 EnumWindows(), 它会重复调用回调:</em>
DllCall(&quot;EnumWindows&quot;, &quot;Ptr&quot;, EnumAddress, &quot;Ptr&quot;, 0)
MsgBox Output  <em>; 显示由回调收集的信息.</em>
    
EnumWindowsProc(hwnd, lParam)
{
    global Output
    title := WinGetTitle("ahk_id " hwnd)
    class := WinGetClass("ahk_id " hwnd)
    if title
        Output .= &quot;HWND: &quot; . hwnd . &quot;`tTitle: &quot; . title . &quot;`tClass: &quot; . class . &quot;`n&quot;
    return true  <em>; 告知 EnumWindows() 继续执行, 一直到枚举完所有的窗口.</em>
}</pre>
</div>

<div class="ex" id="ExSubclassGUI">
<p><a href="#ExSubclassGUI">#2</a>: 下面是个可运行脚本, 它演示了如何在脚本中通过把 GUI 窗口的 WindowProc 重定向到新的 WindowProc 来子类化窗口. 此时, 文本控件的背景颜色被改变为自定义颜色.</p>
<pre>TextBackgroundColor := 0xFFBBBB  <em>; BGR 格式的自定义颜色.</em>
TextBackgroundBrush := DllCall(&quot;CreateSolidBrush&quot;, &quot;UInt&quot;, TextBackgroundColor)

MyGui := Gui.New()
Text := MyGui.Add("Text",, "Here is some text that is given`na custom background color.")

<em>; 64 位脚本必须调用 SetWindowLongPtr 代替 SetWindowLong:</em>
SetWindowLong := A_PtrSize=8 ? "SetWindowLongPtr" : "SetWindowLong"

WindowProcNew := <strong>CallbackCreate</strong>("WindowProc")  <em>; 避免子类化中使用快速模式.</em>
WindowProcOld := DllCall(SetWindowLong, &quot;Ptr&quot;, MyGui.Hwnd, &quot;Int&quot;, -4  <em>; -4 为 GWL_WNDPROC</em>
    , &quot;Ptr&quot;, WindowProcNew, &quot;Ptr&quot;) <em>; 返回值必须设置为 &quot;Ptr&quot; 或 &quot;UPtr&quot; 而不是 &quot;Int&quot;.</em>

MyGui.Show

WindowProc(hwnd, uMsg, wParam, lParam)
{
    Critical
    global Text, TextBackgroundColor, TextBackgroundBrush, WindowProcOld
    if (uMsg = 0x138 &amp;&amp; lParam = Text.Hwnd)  <em>; 0x138 为 WM_CTLCOLORSTATIC.</em>
    {
        DllCall(&quot;SetBkColor&quot;, &quot;Ptr&quot;, wParam, &quot;UInt&quot;, TextBackgroundColor)
        return TextBackgroundBrush  <em>; 返回 HBRUSH 来通知操作系统我们改变了 HDC.</em>
    }
    <em>; 否则(因为上面没有返回), 将所有的未处理事件传递给原来的 WindowProc.</em>
    return DllCall(&quot;CallWindowProc&quot;, &quot;Ptr&quot;, WindowProcOld, &quot;Ptr&quot;, hwnd, &quot;UInt&quot;, uMsg, &quot;Ptr&quot;, wParam, &quot;Ptr&quot;, lParam)
}</pre>
</div>

</body>
</html>